﻿using System;
using System.Diagnostics;
using System.Collections.Generic;

using Microsoft.Xna.Framework;

namespace Nine
{
	public enum SceneFilter
	{
		None = 0,
		Update = 1,
		Draw = 2
	}

	//[DebuggerNonUserCode]
    public abstract class Scene : IUpdateable, IDrawable, IScene
	{
		#region IUpdateable Members

		#region Enabled
		private bool enabled = true;

		public bool Enabled
		{
			get { return this.enabled; }
			set
			{
				if (this.enabled != value)
				{
					this.enabled = value;
					this.OnEnabledChanged(this, EventArgs.Empty);
				}
			}
		}

		protected virtual void OnEnabledChanged(object sender, EventArgs args)
		{
			if (this.EnabledChanged != null)
				this.EnabledChanged(sender, args);
		}

		public event EventHandler<EventArgs> EnabledChanged;
		#endregion

		#region UpdateOrder
		public int UpdateOrder
		{
			get { return 0; }
		}

		public event EventHandler<EventArgs> UpdateOrderChanged;
		#endregion

		#endregion

		#region IDrawable Members

		#region Visible
		private bool visible = true;

		public bool Visible
		{
			get { return this.visible; }
			set
			{
				if (this.visible != value)
				{
					this.visible = value;
					this.OnVisibleChanged(this, EventArgs.Empty);
				}
			}
		}

		protected virtual void OnVisibleChanged(object sender, EventArgs args)
		{
			if (this.VisibleChanged != null)
				this.VisibleChanged(sender, args);
		}

		public event EventHandler<EventArgs> VisibleChanged;
		#endregion

		#region DrawOrder
		public int DrawOrder
		{
			get { return 0; }
		}

		public event EventHandler<EventArgs> DrawOrderChanged;
		#endregion

		#endregion

		#region IScene

		internal bool sort = false;

		private List<Entity> FiredActors = new List<Entity>();
        private List<Entity> EngagedActors = new List<Entity>();

		private List<IDrawable> DrawnActors = new List<IDrawable>();
		private List<IUpdateable> UpdatedActors = new List<IUpdateable>();

        public ISceneManager Manager { get; internal set; }

		public SceneFilter filter = SceneFilter.Update | SceneFilter.Draw;

		public SceneFilter Filter
		{
			get { return filter; }
			set { filter = value; }
		}

        public virtual void Add(Entity entity)
        {
			if (entity.Scene != null)
				throw new ArgumentException("Entity Already belongs to a Scene.", "entity");

			entity.Scene = this;
			this.EngagedActors.Add(entity);
        }

        public virtual void Remove(Entity entity)
        {
            if (entity.Scene != this)
                throw new ArgumentException("Entity not in the Scene.", "entity");
            if (!this.FiredActors.Contains(entity))
                this.FiredActors.Add(entity);
		}

        public virtual void Clear()
        {
			this.FiredActors.Clear();
			foreach (Entity entity in this.UpdatedActors)
				this.FiredActors.Add(entity);
        }

		public virtual void Initialize()
		{
			foreach (Entity entity in UpdatedActors)
				entity.Initialize();
			this.LoadContent();
		}

		protected virtual void LoadContent() { }

		#endregion

        protected internal virtual void OnPushed() { }

        protected internal virtual void OnPoped() { }

		public virtual void Update(GameTime Gt)
        {
            foreach (IUpdateable entity in this.UpdatedActors)
                if (entity.Enabled)
                    entity.Update(Gt);

            if (this.FiredActors.Count > 0)
            {
				foreach (Entity entity in this.FiredActors)
				{
					this.DrawnActors.Remove(entity);
					this.UpdatedActors.Remove(entity);
					entity.Scene = null;
				}
                this.FiredActors.Clear();
            }

            if (this.EngagedActors.Count > 0)
            {
				foreach (Entity entity in this.EngagedActors)
				{
					this.DrawnActors.Add(entity);
					this.UpdatedActors.Add(entity);
				}
                this.EngagedActors.Clear();
				this.sort = true;
            }

			if (this.sort)
			{
				this.DrawnActors.Sort((a, b) => a.DrawOrder - b.DrawOrder);
				this.UpdatedActors.Sort((a, b) => a.UpdateOrder - b.UpdateOrder);
				this.sort = false;
			}
        }

        public virtual void Draw(GameTime Gt)
        {
            foreach (IDrawable entity in DrawnActors)
                if (entity.Visible)
                    entity.Draw(Gt);
        }

		public Scene()
			: this(10) { }

		public Scene(int capacity)
		{
			FiredActors = new List<Entity>(capacity);
			EngagedActors = new List<Entity>(capacity);

			DrawnActors = new List<IDrawable>(capacity);
			UpdatedActors = new List<IUpdateable>(capacity);
		}
	}
}
